﻿/***********************************************************************************************
COPYRIGHT 2008 Vijeth D

This file is part of Handwritten Character Recognition - NeuronDotNet Sample.
(Project Website : http://neurondotnet.freehostia.com)

NeuronDotNet is a free software. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.

NeuronDotNet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with NeuronDotNet.
If not, see <http://www.gnu.org/licenses/>.

***********************************************************************************************/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.Serialization;

namespace NeuronDotNet.Controls
{
    [Serializable]
    public class Letter : ISerializable
    {
        private readonly List<Stroke> strokes = new List<Stroke>();
        int left = int.MaxValue;
        int right = 0;
        int top = int.MaxValue;
        int bottom = 0;

        private bool isUpToDate = true;

        public int StrokesCount
        {
            get { return strokes.Count; }
        }

        public IEnumerable<Stroke> Strokes
        {
            get
            {
                Update();
                for (int i = 0; i < strokes.Count; i++)
                {
                    yield return strokes[i];
                }
            }
        }

        public Rectangle Bounds
        {
            get { return new Rectangle(left, top, right - left, bottom - top); }
        }

        public Letter()
        {
        }

        public Letter(IEnumerable<Stroke> strokes)
            : this()
        {
            Helper.ValidateNotNull(strokes, "strokes");

            foreach (Stroke stroke in strokes)
            {
                AddStroke(new Stroke(stroke));
            }
            isUpToDate = false;
            Update();
        }

        public Letter(Letter letter)
        {
            Helper.ValidateNotNull(letter, "letter");

            for (int i = 0; i < letter.strokes.Count; i++)
            {
                strokes.Add(new Stroke(letter.strokes[i]));
            }
            top = letter.top;
            bottom = letter.bottom;
            left = letter.left;
            right = letter.right;
            Update();
        }

        public Letter(SerializationInfo info, StreamingContext context)
        {
            Helper.ValidateNotNull(info, "info");

            strokes = (List<Stroke>)info.GetValue("strokes", typeof(List<Stroke>));
            left = info.GetInt32("left");
            right = info.GetInt32("right");
            top = info.GetInt32("top");
            bottom = info.GetInt32("bottom");
            isUpToDate = false;
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            Helper.ValidateNotNull(info, "info");
            info.AddValue("strokes", strokes, typeof(List<Stroke>));
            info.AddValue("left", left);
            info.AddValue("right", right);
            info.AddValue("top", top);
            info.AddValue("bottom", bottom);
        }

        public void AddStroke(Stroke stroke)
        {
            Helper.ValidateNotNull(stroke, "stroke");
            strokes.Add(stroke);

            left = Math.Min(left, stroke.Bounds.Left);
            right = Math.Max(right, stroke.Bounds.Right);
            top = Math.Min(top, stroke.Bounds.Top);
            bottom = Math.Max(bottom, stroke.Bounds.Bottom);
            isUpToDate = false;
        }

        protected virtual void Update()
        {
            if (isUpToDate) { return; }

            List<Stroke> splitStrokes = new List<Stroke>();
            for (int i = 0; i < strokes.Count; i++)
            {
                splitStrokes.AddRange(strokes[i].Split());
            }
            strokes.Clear();
            strokes.AddRange(splitStrokes);
            isUpToDate = true;
        }

        public void Clip(Rectangle bounds)
        {
            left = int.MaxValue;
            right = 0;
            top = int.MaxValue;
            bottom = 0;
            for (int i = 0; i < strokes.Count; i++)
            {
                strokes[i].Clip(bounds);
                if (strokes[i].PointsCount == 0)
                {
                    strokes.RemoveAt(i);
                    i--;
                }
                left = Math.Min(left, strokes[i].Bounds.Left);
                right = Math.Max(right, strokes[i].Bounds.Right);
                top = Math.Min(top, strokes[i].Bounds.Top);
                bottom = Math.Max(bottom, strokes[i].Bounds.Bottom);
            }
            isUpToDate = true;
        }

        public double[] GetEquivalentVector(int rowCount, int columnCount)
        {
            return GetEquivalentVector(rowCount, columnCount, false);
        }

        public double[] GetEquivalentVector(int rowCount, int columnCount, bool useRandomOffset)
        {
            int left = this.left;
            int right = this.right;
            int top = this.top;
            int bottom = this.bottom;
            int width = right - left;
            int height = bottom - top;

            if (useRandomOffset)
            {
                left = Math.Max(0, left - (int)(width * Helper.GetRandom() * 0.6));
                right = right + (int)(width * Helper.GetRandom() * 0.3);
                top = Math.Max(0, top - (int)(height * Helper.GetRandom() * 0.6));
                bottom = bottom + (int)(width * Helper.GetRandom() * 0.3);
                width = right - left;
                height = bottom - top;
            }

            int scale = Math.Max(width, height) + 1;
            
            double xUnit = ((double)scale) / (columnCount);
            double yUnit = ((double)scale) / (rowCount);
            double[] data = new double[columnCount * rowCount];

            if (scale < 0)
            {
                return data;
            }

            int[,] map = new int[scale, scale];
            for (int k = 0; k < strokes.Count; k++)
            {
                foreach (Line line in strokes[k].Lines)
                {
                    if (line == null) continue;
                    int xStart = line.Start.X;
                    int yStart = line.Start.Y;

                    int xk = line.Start.X - left;
                    int yk = line.Start.Y - top;

                    int dx = Math.Abs(line.End.X - xStart);
                    int dy = Math.Abs(line.End.Y - yStart);
                    int twoDx = 2 * dx;
                    int twoDy = 2 * dy;

                    int xInc = Math.Sign(line.End.X - xStart);
                    int yInc = Math.Sign(line.End.Y - yStart);

                    if (twoDx >= twoDy)
                    {
                        int pk = twoDy - dx;
                        for (int i = 0; i <= dx; i++, xk += xInc, pk += twoDy)
                        {
                            map[xk, yk] = 1;
                            if (pk >= 0)
                            {
                                pk -= twoDx;
                                yk += yInc;
                            }
                        }
                    }
                    else
                    {
                        int pk = twoDx - dy;
                        for (int i = 0; i <= dy; i++, pk += twoDx, yk += yInc)
                        {
                            map[xk, yk] = 1;
                            if (pk >= 0)
                            {
                                pk -= twoDy;
                                xk += xInc;
                            }
                        }
                    }
                }
            }

            double max = 0d;
            for (int i = 0; i < rowCount; i++)
            {
                for (int j = 0; j < columnCount; j++)
                {
                    int count = 0;
                    int total = 0;
                    for (int y = (int)(i * yUnit); y < (i + 1) * yUnit && y < scale; y++)
                    {
                        for (int x = (int)(j * xUnit); x < (j + 1) * xUnit && x < scale; x++)
                        {
                            count += map[x, y];
                            total++;
                        }
                    }
                    data[i * columnCount + j] = ((double)count) / total;
                    if (max < data[i * columnCount + j])
                    {
                        max = data[i * columnCount + j];
                    }
                }
            }
            if (max > 0)
            {
                for (int i = 0; i < data.Length; i++)
                {
                    data[i] /= max;
                }
            }
            return data;
        }
    }
}